DSS: Streamlining Invoice Processing¶
Document Understanding Transformer for Efficient Business Operations in Python¶
- 💻 Detail Material: Document Understanding Transformer for Efficient Business Operations in Python
- 👩🏻🏫 Instructor: Fiqey Indriati Eka Sari
© Algoritma 2024
Background¶
Di dalam lanskap operasional bisnis yang terus berkembang dengan cepat, menguasai pemrosesan faktur yang efektif menjadi sangat penting. Pemrosesan faktur adalah bagian kritis dari kegiatan bisnis yang melibatkan pengelolaan keuangan dan akuntansi. Faktur yang diproses dengan baik membantu dalam pelacakan pengeluaran, mengoptimalkan arus kas, dan memastikan kepatuhan terhadap peraturan keuangan. Dengan lanskap bisnis yang cepat berubah, perusahaan perlu dapat beradaptasi dengan cepat. Menguasai pemrosesan faktur menjadi penting untuk mengatasi ketidakpastian dan menjaga kecepatan tanggapan terhadap perubahan di sekitarnya.
Materi ini menawarkan solusi komprehensif dengan workshop khusus yang dilengkapi dengan keterampilan terkini. Fokusnya adalah pada penggunaan Document Understanding Transformer, sebuah model deep learning NLP, dengan pemrograman Python. Dengan menyelami teknologi ini, yang terbukti berhasil dalam tugas pemrosesan bahasa alami, peserta akan memperoleh keahlian untuk menyederhanakan dan meningkatkan alur kerja pemrosesan faktur.
Output¶
Setelah menyelesaikan workshop ini, Anda akan dapat:
- Memanfaatkan Document Understanding Transformer (Donut) untuk ekstraksi data yang akurat dari gambar penerimaan, mengoptimalkan alur kerja pemrosesan faktur.
- Meningkatkan keterampilan analisis data untuk pengambilan keputusan yang terinformasi dalam operasional bisnis dengan memanfaatkan hasil keluaran dari Document Understanding Transformer.
- Membangun aplikasi web yang ramah pengguna dengan alat seperti Gradio untuk pemindaian efisien penerimaan dan faktur, secara mulus sejalan dengan kebutuhan bisnis modern untuk proses keuangan yang efisien.
🎯 Training Objectives¶
Python Programming Basics
- Anaconda Environment Preparation
- Working with Jupyter Notebook (.ipynb)
- Understanding Fundamental of Variables & Python Data Types
- Exploring Python Functions and Looping Concepts
Working with DataFrame Pandas
- Understanding Dataframe
pandas - Master Data Manipulation and Pre-processing
- Perform Conditional Subsetting Operations
Document Understanding Transformer (Donut) Utilization
- Introduction to 🍩 OCR-free Document Understanding Transformer (Donut) Model
- Document Information Extraction using Donut
- Data Analysis Enhancement with Donut Outputs
Integration Gradio for Efficient Scanning
- Understand the Fundamentals of Gradio
- Develop Gradio for Efficient Receipt and Invoice Scanning
Cara Membuat Environtment Baru:¶
1️⃣ Membuka Terminal pada Visual Studio Code
Saat pertama kali kita membuka Visual Studio Code, terdapat beberapa menu di pojok kiri atas. Silahkan kamu klik menu Terminal pada bagian kiri atas. Lalu, pilih
New Terminal. Selesai, kamu berhasil membuka terminal baru.2️⃣ Buat Environment dengan Nama
ENV_NAME:conda create -n dss_invoice python=3.10Tujuan dari kita menuliskan
python=3.10agar python yang terinstall pada virtual environtment yang kita buat adalah python dengan versi 3.10.3️⃣ Mengaktifkan Environment
Setelah virtual environtment yang baru kita buat sudah selesai, kita bisa mengaktifkan environtment yang sudah kita buat dengan code berikut.
conda activate dss_invoice
Install/Update Package¶
Sebelum melakukan instalasi package, pastikan Anda sudah melakukan aktivasi virtual environment tempat Anda mau menginstall package.
pip install <PACKAGE_NAME>
Kita juga bisa menginstallnya melalui sebuah teks reqirements.txt yang berisi list packages yang perlu diinstall, commandnya pip install -r requirements.txt dengan syarat sebelum menjalankan command ini, kita sudah berapa pada folder material utama
🧪 Knowledge Check¶
Pilihlah jawaban yang tepat dengan memberikan tanda centang pada kotak.
Berikut adalah tujuan kita membuat virtual environment, KECUALI ...
- Kolaborasi (sharing environment)
- Mengisolasi package beserta versi-nya
- Virtual environment harus dibuat agar Python dapat dijalankan
Pasangkan beberapa opsi ini dengan pilihan yang cocok:
- Visual Studio Code: ...
- Miniconda: ...
- Python: ...
Pilihan Jawaban:
- a. Bahasa Pemrograman
- b. Environment Manager
- c. Integrated Development Environment (IDE)
Working with Jupyter Notebook (.ipynb)¶
Markdown Cell dan Code Cell¶
Tipe cell dalam notebook:
1️⃣ Markdown : untuk menuliskan narasi
Ini adalah cell markdown. Kita bisa menulis teks bold, italic, bahkan formula matematis seperti:
\begin{equation} f(x) = \frac{e^{-x}}{(1+e^{-x})} \end{equation}
Cara menambahkan gambar:
<img src="..." width="500px"></img>
2️⃣ Code : untuk menuliskan script code
💡 symbol # pada cell code berarti adalah sebuah comment. Comment pada cell code tidak akan dieksekusi
# ini merupakan cell untuk code
print("dan ini adalah cell code tempat menuliskan code python")
dan ini adalah cell code tempat menuliskan code python
Untuk run cell code: Ctrl + Alt + Enter
Command Mode and Edit Mode¶
Ada 2 mode cell dalam notebook:
1️⃣ Command Mode
a: Menambah cell baru di atasb: Menambah cell baru di bawahd+d: Menghapus cell terpilihc: Menyalin cell terpilihv: Paste cell terpilihm: Mengubah tipe cell ke markdowny: Mengubah tipe cell ke kodeenter: enter Edit Mode
2️⃣ Edit Mode (Cell Terdapat Border Biru Persegi Panjang)
Ctrl + Enter: eksekusi satu cellEsc: mengubah edit mode menjadi command mode
angka = 10
angka
10
🚀 Mari kita coba buat sebuah objek, yang berisikan nama dss ini!
dss_name = "Data Science Series: Streamlining Invoice Processing"
print(dss_name)
Data Science Series: Streamlining Invoice Processing
Sehingga dss_name dan DSS_Name dimaknai berbeda sehingga akan dianggap variabel yang berbeda pula. Maka dari itu, penamaan variable menjadi hal yang perlu diperhatikan.
## code here
dss_name == dss_name
True
Kode di atas mengembalikan True sebagai output. Cobalah untuk membuat variabel baru dan gunakan True sebagai namanya. kemudian lihat apa yang akan terjadi.
SyntaxError: can't assign to keyword
Sebagai catatan, True, dan juga lawannya, False termasuk ke dalam daftar kata yang dinamakan Python Keywords. Kita tidak dapat menggunakan keyword sebagai nama variabel ataupun sebagai fungsi.
Semua python keyword selain True, False, dan None adalah huruf kecil.
✨ Keywords adalah kata kunci yang sudah ditetapkan oleh Python sebagai nama yang tidak bisa dipakai baik untuk penamaan fungsi, variabel, dan lainnya. Keyword ditulis dalam lower-case (huruf kecil semua) kecuali keyword True, False, dan None. Sejauh ini (Python 3.10) keyword yang ada pada Python adalah sebagai berikut:
# cek daftar keyword
import keyword
keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
⚠️ Berikut beberapa ketentuan dalam memberikan nama variable pada Python:
- Tidak boleh menggunakan angka di awal. Misal
1Algoritma - Hanya kombinasi dari huruf kapital (A-Z), huruf nomina (a-z), angka (0-9).
- Special character
!, $ , &, dlltidak dapat digunakan dalam penamaan variabel. - Bersifat case-sensitive sehingga penamaan variable
algoritma,ALGORITMA, danAlgoritmaadalah 3 variable yang berbeda - Tidak boleh menggunakan keywords pada Python
Python Data Types¶
Untuk mengecek tipe data python dapat menggunakan
type()
(1) 🔠 String¶
Python mewakili string apapun sebagai object str. Adapun beberapa cara untuk membuat nilai string:
- menggunakan
''(contoh:'Jakarta'atau'saya lahir di Bekasi') - mengunakan
""(contoh:"hari jum'at") - menggunakan
'''atau"""(contoh:'''Dia bersorak "Hore! Sudah hari Jum'at"''')
dss_name
'Data Science Series: Streamlining Invoice Processing'
# code here
type(dss_name)
str
(2) 🔢 Number¶
Untuk menyimpan number, python memiliki dua tipe data asli yang disebut int dan float.
intdigunakan untuk menyimpan bilangan bulat (yaitu: 1,2,-3)floatdigunakan untuk menyimpan bilangan real (yaitu: 0.7, -1.8, -1000.0) 👉🏻 bilangan desimal.
# code here
nilai_ta = 100
nilai_fiqey = 90.0
nilai_ta - nilai_fiqey
10.0
✨ Operasi Matematika ✨
+- Penambahan-- Pengurangan*- Perkalian/- Pembagian//- Pembagian yang dibulatkan ke bawah%- Modulus (sisa pembagian)**- Eksponen (pangkat)
(3) ✅ Boolean¶
Boolean hanya berisikan nilai True atau False. Biasanya untuk menunjukkan kebenaran suatu kondisi
2 + 2 == 5
# == --> untuk mengecek kondisi (apakah sama?)
False
(4) 🗃️ List¶
list digunakan untuk menyimpan beberapa nilai dalam python. Untuk membuatnya, cukup letakan nilai di dalam tanda kurung siku []
# list berikut mengandung informasi nama, kota domisili, umur, apakah sudah menikah?
data_list = ['Sari', 'Jakarta', 27, False]
# cek tipe data my_list
type(data_list)
list
✨ Operasi List ✨
Dalam sebuah list kita dapat melakukan beberapa operasi:
list_x.append(a): Menambahkanake dalamlist_x.list_x.remove(a): Menghapus nilaiadarilist_x.
Operasi Indexing:
list_x[i]: Mengakses elemen ke-i darilist_x.
📌 Additional Information
- Python menggunakan sistem zero based indexing yang berarti, urutan pada python dimulai dari angka 0.
# akses informasi kota domilisi dari seorang Sari, dimana informasi kota domilisi terletak di urutan ke-2 (index ke-1)
data_list[1]
'Jakarta'
# coba kita tambahkan informasi pekerjaan 'Sari', yaitu 'Data Scientist'
data_list.append('Data Scientist')
data_list
['Sari', 'Jakarta', 27, False, 'Data Scientist']
(5) 🧾Dictionary¶
dictionary atau dict digunakan untuk menyimpan data dalam bentuk pasangan key-value. Dalam dictionary, setiap key harus unik, dan value-nya bisa berupa berbagai tipe data seperti string, integer, list, atau bahkan dictionary lainnya.
✨Dictionary didefinisikan dengan menggunakan tanda kurung kurawal {}.
# dictionary berisi informasi tentang seseorang
data_dict = {
'nama': 'Sari',
'kota_domisili': 'Jakarta',
'umur': 27,
'menikah': False
}
type(data_dict)
dict
data_dict['kota_domisili']
'Jakarta'
Dictionary memiliki keunggulan daripada list karena memungkinkan penyimpanan data dengan key yang bersifat unik, sehingga memudahkan akses dan manipulasi data berdasarkan identifikasi key, bukan hanya index angka.
📌 Highlight Poin: Tipe Data di Python¶
- String : berisi satu karakter atau lebih, dengan karakter yang bisa berupa huruf, angka, simbol, atau karakter khusus.
- Number :
intuntuk bilangan bulatfloatuntuk bilangan real.
- Boolean : hanya berisikan nilai
TrueatauFalse. - List : untuk menyimpan beberapa nilai dalam Python, dengan sintaks tanda kurung siku
[]. - Dictionary : mirip seperti list, namun
dictionarydalam bentuk pasangan key-value dan menggunakan syntax tanda kurung kurawal{}
Exploring Python Functions and Looping Concepts¶
Function¶
Function merupakan sekelompok perintah yang digunakan untuk melakukan tugas tertentu. Ketika kita melakukan sesuatu yang berulang dan rumit, alangkah baiknya kita menggunakan fungsi agar tidak ada langkah yang berubah maupun penulisan kode yang salah. Penulisan umum sebuah fungsi yaitu:
def nama_fungsi(parameter):
perintah
Pada syntax umum di atas, def merupakan inisiator untuk sebuah fungsi. Sementara hal-hal yang harus kita tentukan yaitu nama dari fungsi, parameter yang akan digunakan di dalamnya, serta perintah atau kode.
Sebagai contoh, kita akan membuat sebuah fungsi luas segitiga:
# fungsi luas_segitiga
def luas_persegi(sisi):
hasil = sisi * sisi
return hasil
# memanggil fungsi
luas_persegi(sisi = 4)
16
Looping (Perulangan)¶
for loop digunakan untuk mengulangi/mengiterasi suatu urutan (dapat berupa list atau string).
Contoh for loop untuk mengiterasi elemen pada list:
jabodetabek = ['Jakarta', 'Bogor', 'Depok', 'Tangerang', 'Bekasi']
# for loop
for x in jabodetabek:
print(x)
Jakarta Bogor Depok Tangerang Bekasi
Working with Dataframe pandas¶
pandas adalah library yang powerful sebagai tools analisis data dan struktur pada Python. Library ini memiliki kemampuan sebagai berikut:
pandasmampu mengolah data menjadi mudah karena mempunyai objek bernama DataFrame.pandasmemiliki function yang mampu mengolah dataframe dengan menerapkan berbagai operasi dan teknik seperti join, agregasi, grouping, dan lain sebagainya
Lebih lengkapnya silahkan kunjungi official documentation
Untuk menggunakan pandas, kita perlu import terlebih dahulu library dengan cara berikut ini:
import pandas as pd
print(pd.__version__)
2.1.4
Understanding Dataframe pandas¶
Read Data¶
Untuk membaca data atau file dengan format .csv dapat menggunakan method .read_csv().
Syntax:
pandas.read_csv("path/data")
🚀 Bacalah data receipts.csv yang berada dalam folder data_input
receipts = pd.read_csv('data_input/receipt_cleaned.csv')
receipts
| receipt_id | item_name | count | price | total_price | |
|---|---|---|---|---|---|
| 0 | ID0000 | real ganache | 1.0 | 16500 | 45500 |
| 1 | ID0000 | egg tart | 1.0 | 13000 | 45500 |
| 2 | ID0000 | pizza toast | 1.0 | 16000 | 45500 |
| 3 | ID0001 | kopi susu kolonel | 1.0 | 23000 | 23000 |
| 4 | ID0002 | s-ovaltine | 1.0 | 20000 | 20000 |
| 5 | ID0003 | m-carame 1 black tea | 1.0 | 28000 | 28000 |
| 6 | ID0004 | bbq chicken | 1.0 | 41000 | 41000 |
| 7 | ID0005 | le mineral | 1.0 | 8000 | 8000 |
| 8 | ID0006 | potato sausage bread | 1.0 | 19000 | 123000 |
| 9 | ID0006 | oreo green tea spread | 1.0 | 52000 | 123000 |
| 10 | ID0006 | white choco banana spread | 1.0 | 52000 | 123000 |
| 11 | ID0007 | choco devil | 4.0 | 63636 | 59500 |
| 12 | ID0007 | cp 360 club card | 1.0 | -9545 | 59500 |
| 13 | ID0008 | talam ungu | 3.0 | 19500 | 11700 |
| 14 | ID0008 | mika kecil | 1.0 | 0 | 11700 |
| 15 | ID0009 | tahu ikan oma giok | 1.0 | 20000 | 20000 |
| 16 | ID0010 | serbu | 2.0 | 40000 | 60000 |
| 17 | ID0010 | choco peanut bread | 2.0 | 20000 | 60000 |
| 18 | ID0011 | se'i sapi sambal matah ( r ) | 1.0 | 20000 | 89100 |
| 19 | ID0011 | se'i s-pi lada hitam (j) | 1.0 | 35000 | 89100 |
| 20 | ID0011 | nasi putih | 2.0 | 10000 | 89100 |
| 21 | ID0011 | milk shake coklat | 1.0 | 16000 | 89100 |
| 22 | ID0012 | es kopi susu | 4.0 | 72000 | 72000 |
| 23 | ID0013 | mineral 600 ml | 1.0 | 7727 | 45500 |
| 24 | ID0013 | bulgogi rice r | 1.0 | 33636 | 45500 |
| 25 | ID0014 | arem arem | 2.0 | 24000 | 39600 |
| 26 | ID0014 | kroket | 1.0 | 12000 | 39600 |
| 27 | ID0015 | arem arem | 2.0 | 24000 | 59400 |
| 28 | ID0015 | pepenero pastel | 2.0 | 30000 | 59400 |
| 29 | ID0016 | tt | 1.0 | 20000 | 20000 |
| 30 | ID0017 | lemonade 16oz | 1.0 | 20000 | 20000 |
| 31 | ID0018 | beef c roll 3pcs | 1.0 | 10000 | 25000 |
| 32 | ID0018 | kaya bred | 1.0 | 15000 | 25000 |
| 33 | ID0019 | futami 17 green tea (clas | 1.0 | 12500 | 42500 |
| 34 | ID0019 | egg tart | 1.0 | 13000 | 42500 |
| 35 | ID0019 | grain croque monsieur | 1.0 | 17000 | 42500 |
Deskripsi Data:
Data receipt_cleaned.csv merupakan data transaksi atau struk pembelian dari suatu tempat seperti kafe atau restoran. Berikut adalah deskripsi untuk setiap kolom dalam data:
receipt_id: Nomor identifikasi unik untuk setiap struk pembelian.item_name: Nama produk atau item yang dibeli.count: Jumlah unit dari suatu produk yang dibeli.price: Harga per unit dari suatu produk.total_price: Total harga untuk suatu produk (jumlah unit dikalikan dengan harga per unit).
Lakukan pengamatan 5 data teratas dari receipts menggunakan .head()
# code here
receipts.head()
| receipt_id | item_name | count | price | total_price | |
|---|---|---|---|---|---|
| 0 | ID0000 | real ganache | 1.0 | 16500 | 45500 |
| 1 | ID0000 | egg tart | 1.0 | 13000 | 45500 |
| 2 | ID0000 | pizza toast | 1.0 | 16000 | 45500 |
| 3 | ID0001 | kopi susu kolonel | 1.0 | 23000 | 23000 |
| 4 | ID0002 | s-ovaltine | 1.0 | 20000 | 20000 |
pandas Data Types¶
- Dataframe merupakan tabel/data tabular dua dimensi yaitu baris dan kolom.
- Dataframe terdiri dari beberapa Series (kolom).
- Dalam satu series harus memiliki tipe data yang sama.
pandasakan menentukan tipe data dari masing-masing Series, tapi hasil dari pandas tidak selalu benar.
Berikut rangkuman tipe data pandas:
💡 Notes: Fokus pada kolom Pandas dtype dan Usage
| Pandas dtype | Python type | Usage |
|---|---|---|
| object, str | str | Text or mixed numeric and non-numeric values |
| int64 | int | Integer numbers |
| float64 | float | Floating point numbers |
| bool | bool | True/False values |
| datetime64[ns] | - | Date and time values |
| timedelta[ns] | - | Differences between two datetimes |
| category | - | Finite list of text values |
Referensi: Overview of Pandas Data Types
Tipe data Pandas:
int64: Integer (bilangan bulat, tanpa koma)float64: Bilangan desimal (berkoma)object: Text (string)category: Kategorikaldatetime64[ns]: Data waktu
Karakteristik tipe data category :
- Dapat dikelompokkan menjadi beberapa kelompok (category)
- Nilainya berulang
Saat kita membaca data dengan pd.read_csv(), pandas akan mencoba menentukan tipe data dari setiap kolom. Lakukan investigasi awal untuk melihat struktur data terhadap object DataFrame dengan menggunakan method .info().
# cek tipe data
receipts.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 36 entries, 0 to 35 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 receipt_id 36 non-null object 1 item_name 36 non-null object 2 count 36 non-null float64 3 price 36 non-null int64 4 total_price 36 non-null int64 dtypes: float64(1), int64(2), object(2) memory usage: 1.5+ KB
💡 NOTES
Dengan menggunakan method .info(), kita dapat memeriksa informasi lengkap dari DataFrame kita:
- Dimensi data: jumlah baris dan kolom (
.shape) - Nama kolom (
.columns) - Tipe data setiap kolom (
.dtypes) - Penggunaan memori
Kolom manakah yang memiliki format tipe data yang belum sesuai?
Jawaban:
item_name-> category
Untuk mengubah tipe data pada pandas, dapat menggunakan method astype().
Syntax
df['column_name'] = df['column_name'].astype('new_data_types')
# check data type
receipts['item_name'] = receipts['item_name'].astype('category')
receipts.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 36 entries, 0 to 35 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 receipt_id 36 non-null category 1 item_name 36 non-null category 2 count 36 non-null float64 3 price 36 non-null int64 4 total_price 36 non-null int64 dtypes: category(2), float64(1), int64(2) memory usage: 3.1 KB
Additional: Object & Categorical Variables¶
Karakteristik tipe data object:
- Mirip seperti tipe data string pada python
- Kemunculannya unik (artinya setiap row kemunculannya hanya 1 atau beberapa) Contoh: ID, NIK, No.HP
Karakteristik tipe data category:
- Dapat dikelompokkan menjadi beberapa kelompok (category)
- Kemunculannya berulang Contoh: Gender, Nama Barang dalam data transaksi, provinsi, jenis kartu kredit, kategori barang: elektronik dan non elektronik
Dua alasan mengapa kita perlu menggunakan tipe data categorical:
- Business Side: memudahkan Analyst untuk memilih metode statistik atau tipe plot mana yang digunakan untuk mengolah data.
- Technical Side: menghemat memori dan menambah kecepatan komputasional
💡 Kita bisa menggunakan method berikut untuk mengidentifikasi kolom mana yang cocok untuk disimpan ke tipe data category
.unique()untuk melihat nilai unik di setiap Series/kolom.nunique()untuk melihat jumlah nilai unik/distict pada Series/Dataframe
Berikut contoh syntax untuk mengecek nilai unik pada sebuah Series
df['nama_kolom'].unique()
Master Data Manipulation & Pre-processing¶
Dalam analisis data, manipulasi dan wrangling data adalah langkah awal kritis yang menentukan keberhasilan analisis. Manipulasi data memungkinkan pengaturan dan filtering data menjadi format yang siap analisis, sementara wrangling data memastikan kebersihan dan keakuratan data. Kedua proses ini fundamental dalam mengubah data mentah menjadi wawasan yang berharga dan dapat diandalkan.
Dive Deeper into String Data Types¶
✨ Dalam proses pembersihan data, terutama pada kolom dengan tipe data string yang berasal dari object dalam DataFrame Pandas, operasi seperti lower(), upper(), dan replace() sering digunakan untuk menyederhanakan atau menstandarisasi teks.
🔻 .str.lower(): digunakan untuk mengubah semua karakter menjadi huruf kecil
# mengubah semua teks dalam kolom 'item_name' menjadi huruf kecil
receipts['item_name'].str.lower()[5]
'm-carame 1 black tea'
🔻 upper(): digunakan untuk mengubah semua karakter menjadi huruf kapital
# mengubah semua teks dalam kolom 'receipt_id' menjadi huruf besar
receipts['receipt_id'].str.upper()[5]
'ID0003'
🔻 replace(): digunakan untuk mengganti karakter tertentu dari sebuah string
# mengganti '-' dengan ' ' (spasi) dalam kolom 'item_name'
receipts['item_name'].str.replace('-', ' ')[5]
'm carame 1 black tea'
Contingency Tables / Frequency Tables¶
Untuk menghitung contigency tables, kita dapat menggunakan method .value_counts().
Kegunaan: Menghitung banyak baris pada setiap category dalam 1 kolom, dan defaultnya diurutkan secara descending
Parameter:
ascending=True: urutkan nilai dalam urutan menaik
Mari kita melihat jumlah pengiriman setiap bulannya
receipts['receipt_id'].value_counts()
receipt_id ID0011 4 ID0000 3 ID0006 3 ID0019 3 ID0007 2 ID0008 2 ID0013 2 ID0014 2 ID0015 2 ID0018 2 ID0010 2 ID0004 1 ID0005 1 ID0003 1 ID0009 1 ID0001 1 ID0002 1 ID0012 1 ID0016 1 ID0017 1 Name: count, dtype: int64
❓ Mari kita lihat barang apa yang sering dibeli?
# code here
receipts['item_name'].value_counts(ascending = False)
item_name arem arem 2 egg tart 2 real ganache 1 se'i s-pi lada hitam (j) 1 nasi putih 1 milk shake coklat 1 es kopi susu 1 mineral 600 ml 1 bulgogi rice r 1 kroket 1 pepenero pastel 1 tt 1 lemonade 16oz 1 beef c roll 3pcs 1 kaya bred 1 futami 17 green tea (clas 1 se'i sapi sambal matah ( r ) 1 choco peanut bread 1 serbu 1 tahu ikan oma giok 1 mika kecil 1 talam ungu 1 cp 360 club card 1 choco devil 1 white choco banana spread 1 oreo green tea spread 1 potato sausage bread 1 le mineral 1 bbq chicken 1 m-carame 1 black tea 1 s-ovaltine 1 kopi susu kolonel 1 pizza toast 1 grain croque monsieur 1 Name: count, dtype: int64
Perform Conditional Subsetting Operations¶
Conditional subsetting bermaksud untuk mengambil sebagian data dari DataFrame berdasarkan suatu kondisi tertentu, seperti:
- Pengiriman yang terjadi pada bulan Januari
- Pengiriman dengan jarak lebih dari 10km
- Pengiriman yang memiliki biaya
add_cost, atauadd_cost != 0
Syntax penulisan untuk conditional subsetting adalah:
df[df['column_name'] <comparison_operator> <value>]
atau
df[df.column_name <comparison_operator> <value>]
Contoh comparison_operator adalah seperti ==, !=, >, >=, <, <=.
Menampilkan data yang terjadi pada hari Senin
receipts[receipts['item_name'] == 'arem arem']
kondisi -> receipts['item_name'] == 'arem arem'
receipts[receipts['item_name'] == 'arem arem']
| receipt_id | item_name | count | price | total_price | |
|---|---|---|---|---|---|
| 25 | ID0014 | arem arem | 2.0 | 24000 | 39600 |
| 27 | ID0015 | arem arem | 2.0 | 24000 | 59400 |
❓ Menampilkan data pembelian yang memiliki receipt_id ID0015
# code here
receipts[receipts['receipt_id'] == 'ID0015']
| receipt_id | item_name | count | price | total_price | |
|---|---|---|---|---|---|
| 27 | ID0015 | arem arem | 2.0 | 24000 | 59400 |
| 28 | ID0015 | pepenero pastel | 2.0 | 30000 | 59400 |
❓ Menampilkan data pembelian yang memiliki total pengeluaran lebih dari > Rp 50.000
# code here
receipt_above50k = receipts[receipts['total_price'] > 50000]
receipt_above50k
| receipt_id | item_name | count | price | total_price | |
|---|---|---|---|---|---|
| 8 | ID0006 | potato sausage bread | 1.0 | 19000 | 123000 |
| 9 | ID0006 | oreo green tea spread | 1.0 | 52000 | 123000 |
| 10 | ID0006 | white choco banana spread | 1.0 | 52000 | 123000 |
| 11 | ID0007 | choco devil | 4.0 | 63636 | 59500 |
| 12 | ID0007 | cp 360 club card | 1.0 | -9545 | 59500 |
| 16 | ID0010 | serbu | 2.0 | 40000 | 60000 |
| 17 | ID0010 | choco peanut bread | 2.0 | 20000 | 60000 |
| 18 | ID0011 | se'i sapi sambal matah ( r ) | 1.0 | 20000 | 89100 |
| 19 | ID0011 | se'i s-pi lada hitam (j) | 1.0 | 35000 | 89100 |
| 20 | ID0011 | nasi putih | 2.0 | 10000 | 89100 |
| 21 | ID0011 | milk shake coklat | 1.0 | 16000 | 89100 |
| 22 | ID0012 | es kopi susu | 4.0 | 72000 | 72000 |
| 27 | ID0015 | arem arem | 2.0 | 24000 | 59400 |
| 28 | ID0015 | pepenero pastel | 2.0 | 30000 | 59400 |
# optional: mengambil yang unique saja
receipt_above50k['receipt_id'].unique()
['ID0006', 'ID0007', 'ID0010', 'ID0011', 'ID0012', 'ID0015'] Categories (20, object): ['ID0000', 'ID0001', 'ID0002', 'ID0003', ..., 'ID0016', 'ID0017', 'ID0018', 'ID0019']
Document Understanding Transformer (DUT) Utilization¶
Dalam bisnis, kebutuhan untuk mengubah gambar menjadi teks sering muncul, yang awalnya diatasi dengan teknologi OCR (Optical Character Recognition).
Namun, OCR memiliki beberapa kelemahan, yaitu:
- Memerlukan langkah-langkah preprocessing yang cukup dalam sebelum diterapkan pada OCR

- Beberapa sistem OCR terbatas dalam mengenali berbagai bahasa atau karakter khusus.
Seiring berkembangnya riset, muncul model OCR-free Document Understanding Transformer (Donut) yang mengintegrasikan arsitektur Transformer tanpa bergantung pada OCR, memungkinkan ekstraksi teks dari gambar dengan lebih akurat dan tanpa batasan bahasa.
Introduction to 🍩 Donut Model¶
What is 🍩 Donut Model?¶
Donut adalah model berbasis Transformer yang dirancang untuk tugas pemahaman dokumen yang tidak menggunakan OCR (Optical Character Recognition), menggunakan teknik mengekstrak informasi bermakna dari gambar dokumen yang tidak terstruktur.
Proses mengubah dokumen gambar menjadi output text/JSON yang dilakukan oleh model Donut melalui beberapa langkah:

Ini meningkatkan efisiensi dan akurasi dalam pengolahan dokumen, terutama untuk dokumen dengan layout kompleks.
Transformer adalah sebuah arsitektur neural network yang diperkenalkan pada tahun 2017 dalam makalah berjudul "Attention Is All You Need" oleh Ashish Vaswani dan tim peneliti dari Google AI.
Arsitektur Transformer adalah terobosan besar dalam bidang natural language processing (NLP) dan telah mengubah cara kita mendekati tugas-tugas seperti machine translation, NLP, dan banyak aplikasi lain yang melibatkan pemrosesan sequential data. Contoh model Transformer seperti BERT, GPT (Generative Pre-trained Transformer), dan T5 (Text-to-Text Transfer Transformer)
✨ State-of-The-Art Transformer: ✨
- Mampu memahami konteks dari data sequential (misal mengetahui makna dari suatu kalimat) dengan konsep "Attention"
- Mampu bekerja secara pararel.
- Memperhatikan urutan dari data menggunakan Positional Encoding.
- Skalabilitas tinggi
- Dapat digunakan dalam berbagai konteks, termasuk pemrosesan gambar, suara, dan natural language.
Why Use 🍩 Donut Model?¶
Model Donut lebih efektif daripada OCR tradisional dalam mengenali teks dari gambar dokumen, terutama yang memiliki layout kompleks atau kualitas gambar yang rendah. Donut dapat memahami konteks dan layout dokumen secara lebih akurat dan cepat.
Selain document information extraction, Donut Model dapat melakukan berbagai tugas pemrosesan dokumen lainnya, termasuk:
- Klasifikasi Dokumen: Mengkategorikan dokumen ke dalam berbagai jenis atau kelas berdasarkan isi dan strukturnya.
- QnA: Menganalisis dan menjawab pertanyaan yang berkaitan dengan konten dalam dokumen.
💡 Tak hanya itu, fakta menarik dari model Donut adalah bahwa model ini dilatih menggunakan teks dalam berbagai bahasa dengan memanfaatkan SynthDoG. SynthDoG adalah sebuah generator dokumen sintetis yang membantu pelatihan model Donut untuk fleksibel dalam berbagai bahasa dan domain.

Dengan menggunakan SynthDoG, Donut tidak hanya efektif dalam memahami dokumen dalam bahasa Inggris, tetapi juga dalam bahasa lain, memberikan kemampuan pemahaman dokumen yang lebih luas dan serbaguna.
How to Use 🍩 Donut Model for Document Information Extraction?¶
Model Donut bisa digunakan dalam Python dengan syarat kita telah melakukan instalasi yang diperlukan dan melakukan impor library yang diperlukan.
import re
import os
import torch
from PIL import Image
from transformers import DonutProcessor, VisionEncoderDecoderModel
c:\Users\user\miniconda3\envs\dss_donut\lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm
💡 Notes:
DonutProcessoradalah kelas yang digunakan untuk memproses input untuk model Donut.VisionEncoderDecoderModeladalah kelas yang mengimplementasikan arsitektur model Vision Encoder-Decoder, yang dapat digunakan untuk tugas pengolahan citra dan natural language processing.
🔻Mari kita load model donut yang akan kita gunakan
⚠️ It will take longer if it's your first time loading it.
processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
Could not find image processor class in the image processor config or the model config. Loading based on pattern matching with the model's feature extractor configuration. c:\Users\user\miniconda3\envs\dss_donut\lib\site-packages\torch\_utils.py:831: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage() return self.fget.__get__(instance, owner)()
# set model device
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
VisionEncoderDecoderModel(
(encoder): DonutSwinModel(
(embeddings): DonutSwinEmbeddings(
(patch_embeddings): DonutSwinPatchEmbeddings(
(projection): Conv2d(3, 128, kernel_size=(4, 4), stride=(4, 4))
)
(norm): LayerNorm((128,), eps=1e-05, elementwise_affine=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(encoder): DonutSwinEncoder(
(layers): ModuleList(
(0): DonutSwinStage(
(blocks): ModuleList(
(0-1): 2 x DonutSwinLayer(
(layernorm_before): LayerNorm((128,), eps=1e-05, elementwise_affine=True)
(attention): DonutSwinAttention(
(self): DonutSwinSelfAttention(
(query): Linear(in_features=128, out_features=128, bias=True)
(key): Linear(in_features=128, out_features=128, bias=True)
(value): Linear(in_features=128, out_features=128, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): DonutSwinSelfOutput(
(dense): Linear(in_features=128, out_features=128, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): DonutSwinDropPath(p=0.1)
(layernorm_after): LayerNorm((128,), eps=1e-05, elementwise_affine=True)
(intermediate): DonutSwinIntermediate(
(dense): Linear(in_features=128, out_features=512, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): DonutSwinOutput(
(dense): Linear(in_features=512, out_features=128, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
)
(downsample): DonutSwinPatchMerging(
(reduction): Linear(in_features=512, out_features=256, bias=False)
(norm): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
)
)
(1): DonutSwinStage(
(blocks): ModuleList(
(0-1): 2 x DonutSwinLayer(
(layernorm_before): LayerNorm((256,), eps=1e-05, elementwise_affine=True)
(attention): DonutSwinAttention(
(self): DonutSwinSelfAttention(
(query): Linear(in_features=256, out_features=256, bias=True)
(key): Linear(in_features=256, out_features=256, bias=True)
(value): Linear(in_features=256, out_features=256, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): DonutSwinSelfOutput(
(dense): Linear(in_features=256, out_features=256, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): DonutSwinDropPath(p=0.1)
(layernorm_after): LayerNorm((256,), eps=1e-05, elementwise_affine=True)
(intermediate): DonutSwinIntermediate(
(dense): Linear(in_features=256, out_features=1024, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): DonutSwinOutput(
(dense): Linear(in_features=1024, out_features=256, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
)
(downsample): DonutSwinPatchMerging(
(reduction): Linear(in_features=1024, out_features=512, bias=False)
(norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
)
)
(2): DonutSwinStage(
(blocks): ModuleList(
(0-13): 14 x DonutSwinLayer(
(layernorm_before): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
(attention): DonutSwinAttention(
(self): DonutSwinSelfAttention(
(query): Linear(in_features=512, out_features=512, bias=True)
(key): Linear(in_features=512, out_features=512, bias=True)
(value): Linear(in_features=512, out_features=512, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): DonutSwinSelfOutput(
(dense): Linear(in_features=512, out_features=512, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): DonutSwinDropPath(p=0.1)
(layernorm_after): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
(intermediate): DonutSwinIntermediate(
(dense): Linear(in_features=512, out_features=2048, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): DonutSwinOutput(
(dense): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
)
(downsample): DonutSwinPatchMerging(
(reduction): Linear(in_features=2048, out_features=1024, bias=False)
(norm): LayerNorm((2048,), eps=1e-05, elementwise_affine=True)
)
)
(3): DonutSwinStage(
(blocks): ModuleList(
(0-1): 2 x DonutSwinLayer(
(layernorm_before): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
(attention): DonutSwinAttention(
(self): DonutSwinSelfAttention(
(query): Linear(in_features=1024, out_features=1024, bias=True)
(key): Linear(in_features=1024, out_features=1024, bias=True)
(value): Linear(in_features=1024, out_features=1024, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
(output): DonutSwinSelfOutput(
(dense): Linear(in_features=1024, out_features=1024, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
(drop_path): DonutSwinDropPath(p=0.1)
(layernorm_after): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
(intermediate): DonutSwinIntermediate(
(dense): Linear(in_features=1024, out_features=4096, bias=True)
(intermediate_act_fn): GELUActivation()
)
(output): DonutSwinOutput(
(dense): Linear(in_features=4096, out_features=1024, bias=True)
(dropout): Dropout(p=0.0, inplace=False)
)
)
)
)
)
)
(pooler): AdaptiveAvgPool1d(output_size=1)
)
(decoder): MBartForCausalLM(
(model): MBartDecoderWrapper(
(decoder): MBartDecoder(
(embed_tokens): Embedding(57580, 1024, padding_idx=1)
(embed_positions): MBartLearnedPositionalEmbedding(770, 1024)
(layers): ModuleList(
(0-3): 4 x MBartDecoderLayer(
(self_attn): MBartAttention(
(k_proj): Linear(in_features=1024, out_features=1024, bias=True)
(v_proj): Linear(in_features=1024, out_features=1024, bias=True)
(q_proj): Linear(in_features=1024, out_features=1024, bias=True)
(out_proj): Linear(in_features=1024, out_features=1024, bias=True)
)
(activation_fn): GELUActivation()
(self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
(encoder_attn): MBartAttention(
(k_proj): Linear(in_features=1024, out_features=1024, bias=True)
(v_proj): Linear(in_features=1024, out_features=1024, bias=True)
(q_proj): Linear(in_features=1024, out_features=1024, bias=True)
(out_proj): Linear(in_features=1024, out_features=1024, bias=True)
)
(encoder_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
(fc1): Linear(in_features=1024, out_features=4096, bias=True)
(fc2): Linear(in_features=4096, out_features=1024, bias=True)
(final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
)
)
(layernorm_embedding): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
(layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
)
)
(lm_head): Linear(in_features=1024, out_features=57580, bias=False)
)
)
Document Information Extraction Using Donut¶

Tentu, untuk melakukan ekstraksi informasi dari dokumen, langkah pertama yang perlu dilakukan adalah menyiapkan gambar-gambar dokumen yang akan dibaca. Oleh karena itu, kita akan mencoba membaca beberapa file invoice yang terdapat pada folder data_input.
1️⃣ Read Document Images¶
- Persiapkan dimana letak peletakan file-file gambar dokumennya.
# folder path
dir_path = 'data_input/valid/'
- Mari membuat dua list,
img_filenamesuntuk menyimpan nama file dengan ekstensi .png,labelsuntuk menyimpan nama file dengan ekstensi .txt
Hasilnya, dua list tersebut berisi nama file gambar dan label teks dari dokumen gambar yang kita miliki.
img_filenames = [] # list nama file gambar
images = [] # list objek gambar
labels = [] # list label dari gambar
# iterasi direktori
for file in os.listdir(dir_path):
if file.endswith(('.png', '.jpg', '.jpeg')):
# menyimpan nama invoice ke dalam list
img_filenames.append(file)
# membuka dan menyimpan objek gambar ke dalam list
images.append(Image.open(dir_path + file))
elif file.endswith('.txt'):
# membuka file teks dan membaca isinya, lalu menyimpan ke dalam list
with open(dir_path + file, 'r') as label_file:
labels.append(label_file.read())
# mencetak list nama file gambar
print(img_filenames)
['ID0000.png', 'ID0001.png', 'ID0002.png', 'ID0003.png', 'ID0004.png', 'ID0005.png', 'ID0006.png', 'ID0007.png', 'ID0008.png', 'ID0009.png', 'ID0010.png', 'ID0011.png', 'ID0012.png', 'ID0013.png', 'ID0014.png', 'ID0015.png', 'ID0016.png', 'ID0017.png', 'ID0018.png', 'ID0019.png', 'ID0020.png', 'ID0021.png', 'ID0022.png', 'ID0023.png', 'ID0024.png', 'ID0025.png', 'ID0026.png', 'ID0027.png', 'ID0028.png', 'ID0029.png', 'ID0030.png', 'ID0031.png', 'ID0032.png', 'ID0033.png', 'ID0034.png', 'ID0035.png', 'ID0036.png', 'ID0037.png', 'ID0038.png', 'ID0039.png', 'ID0040.png', 'ID0041.png', 'ID0042.png', 'ID0043.png', 'ID0044.png', 'ID0045.png', 'ID0046.png', 'ID0047.png', 'ID0048.png', 'ID0049.png', 'ID0050.png', 'ID0051.png', 'ID0052.png', 'ID0053.png', 'ID0054.png', 'ID0055.png', 'ID0056.png', 'ID0057.png', 'ID0058.png', 'ID0059.png', 'ID0060.png', 'ID0061.png', 'ID0062.png', 'ID0063.png', 'ID0064.png', 'ID0065.png', 'ID0066.png', 'ID0067.png', 'ID0068.png', 'ID0069.png', 'ID0070.png', 'ID0071.png', 'ID0072.png', 'ID0073.png', 'ID0074.png', 'ID0075.png', 'ID0076.png', 'ID0077.png', 'ID0078.png', 'ID0079.png', 'ID0080.png', 'ID0081.png', 'ID0082.png', 'ID0083.png', 'ID0084.png', 'ID0085.png', 'ID0086.png', 'ID0087.png', 'ID0088.png', 'ID0089.png', 'ID0090.png', 'ID0091.png', 'ID0092.png', 'ID0093.png', 'ID0094.png', 'ID0095.png', 'ID0096.png', 'ID0097.png', 'ID0098.png', 'ID0099.png']
🔻 Mari coba kita lihat label dan gambar ke-7
# code here
print(labels[6])
images[6]
{'menu': [{'nm': 'POTATO SAUSAGE BREAD', 'cnt': '1', 'price': '19,000'}, {'nm': 'OREO GREEN TEA SPREAD', 'cnt': '1', 'price': '52,000'}, {'nm': 'WHITE CHOCO BANANA SPREAD', 'cnt': '1', 'price': '52,000'}], 'total': {'total_price': '123,000', 'creditcardprice': '123,000'}}
2️⃣ Document Pre-processing¶
Sebagaimana umumnya pada model deep learning, diperlukan penyesuaian bentuk input sebelum dapat digunakan untuk prediksi atau pemrosesan lebih lanjut. Dalam konteks Donut Model, tahap ini melibatkan konversi gambar ke dalam format tensor dengan cara yang sederhana menggunakan alat yang disebut Donut Processor.
🎯 Proses ini diperlukan karena model, terutama yang menggunakan framework seperti PyTorch, umumnya membutuhkan input dalam bentuk tensor.
images[6].size
(864, 1296)
pixel_values = processor(images[6], return_tensors="pt").pixel_values
print(pixel_values.shape)
torch.Size([1, 3, 1280, 960])
✨ Keterangan:
Makna ukuran pada tensor tersebut adalah [jumlah batch, jumlah channel (RGB), tinggi, lebar]. Dalam konteks ini, semua gambar diproses menjadi ukuran yang sama sebelum masuk ke model. Hal ini dilakukan karena model deep learning seringkali mensyaratkan agar seluruh input memiliki dimensi yang konsisten.
3️⃣ Sequence Generation¶
Selanjutnya, kita membiarkan model Donut secara otoregresif menghasilkan data terstruktur, mengggunakan metode .generate().
task_prompt = "<s_cord-v2>"
✨ Secara umum, model Donut dapat digunakan untuk berbagai tugas, seperti:
- Pemrosesan/Ekstraksi Dokumen (
<s_cord-v2) - Klasifikasi Dokumen
- Pertanyaan dan Jawaban Visual (VQA) pada Dokumen

Oleh karena itu, ketika ingin menggunakan model untuk salah satu dari tugas-tugas tersebut, kita perlu menentukan task_prompt agar mengarahkan model Donut untuk fokus pada tugas spesifik yang diinginkan, sehingga output yang dihasilkan dapat lebih relevan dan bermakna sesuai dengan kebutuhan pengguna.
Selengkapnya dapat di cek di Official Github Donut
processor.tokenizer.pad_token_id
1
outputs = model.generate(
inputs=pixel_values.to(device),
max_length=model.decoder.config.max_position_embeddings,
decoder_input_ids=processor.tokenizer(task_prompt, add_special_tokens=False, return_tensors="pt")["input_ids"].to(device),
pad_token_id=processor.tokenizer.pad_token_id,
eos_token_id=processor.tokenizer.eos_token_id,
bad_words_ids=[[processor.tokenizer.unk_token_id]],
return_dict_in_generate=True,
use_cache=True,
# modify parameters
early_stopping=True,
num_beams=2,
output_scores=True,
)
✨ Fungsi model.generate() pada model Donut memungkinkan untuk menghasilkan sequence text dari model. Parameter utama-utama adalah berikut
inputs: Nilai piksel dari gambar yang digunakan sebagai input untuk di-generate from doc to text.max_length: Panjang maksimum dari urutan yang dihasilkan.decoder_input_ids: ID input untuk dekoder. Di sini, tokenizer memprosestask_prompt(prompt tugas yang ditentukan) dan menghasilkan ID yang relevan.add_special_tokens=FalseMenunjukkan bahwa token khusus tidak ditambahkan dalam proses tokenisasi.pad_token_id: ID dari token padding.eos_token_id: ID dari token akhir urutan.bad_words_ids: Daftar ID kata yang harus dihindari oleh model saat melakukan generasi.return_dict_in_generate: Mengembalikan output dalam format dictionary yang lebih mudah diakses.use_cache: Menentukan apakah hasil perhitungan sebelumnya harus disimpan untuk digunakan kembali, untuk meningkatkan efisiensi.
Parameter tambahannya adalah:
early_stopping: Jika diaturTrue, model akan berhenti menghasilkan output lebih lanjut jika syarat tertentu terpenuhi, seperti mencapai EOS token.num_beams: Jumlah beams yang digunakan dalam pencarian beam.output_scores: Apakah mengembalikan skor skor untuk setiap token yang dihasilkan bersamaan dengan teks.
📌 Untuk informasi lebih rinci, silahkan merujuk ke dokumentasi 🍩 Donut di sini.
4️⃣ Document Post-Processing: Sequence Token Cleaning¶
🔻 Output hasil generate dokumen menggunakan model Donut adalah tensor yang berisi sequence token-token yang perlu dilakukan decode sehingga kita dapat mengetahui isinya. Setiap angka dalam tensor ini merepresentasikan suatu token dalam model.
outputs.sequences
tensor([[57579, 57526, 57528, 40769, 44398, 54165, 13663, 56992, 50990, 42990,
43547, 5909, 57527, 57530, 1314, 57529, 57532, 17888, 56239, 57531,
57522, 57528, 37040, 49429, 42990, 51290, 4004, 42990, 4008, 5176,
40099, 39415, 57527, 57530, 1314, 57529, 57532, 20017, 52066, 57531,
57522, 57528, 46193, 53278, 33938, 36277, 18081, 56701, 42990, 40266,
5178, 5176, 40099, 39415, 57527, 57530, 1314, 57529, 57532, 20017,
52066, 57531, 57525, 57544, 57546, 40474, 47108, 57545, 57564, 40474,
47108, 57563, 57543, 2]])
🔻Maka dari itu, mari kita lakukan decode menggunakan processor yang sebelumnya juga digunakan untuk mengencode dengan menggunakan perintah processor.batch_decode()
raw_sequence = processor.batch_decode(outputs.sequences)[0]
print(raw_sequence)
<s_cord-v2><s_menu><s_nm> POTATO SAUSAGE BREAD</s_nm><s_cnt> 1</s_cnt><s_price> 19,000</s_price><sep/><s_nm> OREO GREEN TEA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price><sep/><s_nm> WHITE CHOCO BANANA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price></s_menu><s_total><s_total_price> 123,000</s_total_price><s_creditcardprice> 123,000</s_creditcardprice></s_total></s>
✨ Ini adalah teks terstruktur yang telah didekode dari tensor. Teks ini mencakup informasi terkait pesanan menu, jumlah, harga, dan total pembayaran. Strukturnya mencakup tag-tag XML (<s_cord-v2>, <s_menu>, <s_nm>, <s_cnt>, <s_price>, <sep/>, <s_total>, <s_total_price>, <s_creditcardprice>) yang memberikan konteks terhadap data yang terkandung.
🔻 Selanjutnya, dilakukan pembersihan terhadap teks. Tahap ini mencakup penghapusan token EOS (</s>), token PAD (<pad>), dan tag pertama <s_cord-v2> yang menandakan awal dari tugas pertama
sequence = raw_sequence.replace(processor.tokenizer.eos_token, "").replace(processor.tokenizer.pad_token, "") # remove </s> eos and <pad> token
sequence = re.sub(r"<.*?>", "", sequence, count=1).strip() # remove <s_cord-v2> first task start token
sequence
'<s_menu><s_nm> POTATO SAUSAGE BREAD</s_nm><s_cnt> 1</s_cnt><s_price> 19,000</s_price><sep/><s_nm> OREO GREEN TEA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price><sep/><s_nm> WHITE CHOCO BANANA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price></s_menu><s_total><s_total_price> 123,000</s_total_price><s_creditcardprice> 123,000</s_creditcardprice></s_total>'
5️⃣ Output Conversion: Token to JSON¶
✨ Output dari model Donut yang dihasilkan dapat kita ubah menjadi format JSON (JavaScript Object Notation), di Python mirip dengan dictionary, untuk memudahkan pengolahan dan analisis lebih lanjut. Format JSON/ dictionary memungkinkan representasi data yang terstruktur dan mudah diakses.
processor.token2json(sequence)
{'menu': [{'nm': 'POTATO SAUSAGE BREAD', 'cnt': '1', 'price': '19,000'},
{'nm': 'OREO GREEN TEA SPREAD', 'cnt': '1', 'price': '52,000'},
{'nm': 'WHITE CHOCO BANANA SPREAD', 'cnt': '1', 'price': '52,000'}],
'total': {'total_price': '123,000', 'creditcardprice': '123,000'}}
# gambar yang di generate (images[6])
images[6]
Di sini, kita akan mencapai hasil akhir dari proses generasi informasi dokumen menggunakan model Donut.
🍩 All-in-One Function to Generate Document Information¶
💡 Untuk memudahkan dan mengotomatisasi proses ini, khususnya ketika bekerja dengan banyak gambar, kita akan menggabungkan seluruh langkah, dari 1 hingga 5, ke dalam satu fungsi. Dengan demikian, kita dapat dengan mudah menerapkan fungsi ini pada banyak gambar secara berurutan melalui looping.
def doc_to_text(input_img, task_prompt=task_prompt, model=model, processor=processor):
# set model device
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
# document preprocessing
pixel_values = processor(input_img, return_tensors="pt").pixel_values
decoder_input_ids = processor.tokenizer(task_prompt, add_special_tokens=False, return_tensors="pt")["input_ids"]
# sequence generation
outputs = model.generate(
pixel_values.to(device),
decoder_input_ids=decoder_input_ids.to(device),
max_length=model.decoder.config.max_position_embeddings,
pad_token_id=processor.tokenizer.pad_token_id,
eos_token_id=processor.tokenizer.eos_token_id,
use_cache=True,
bad_words_ids=[[processor.tokenizer.unk_token_id]],
return_dict_in_generate=True,
# modify parameters
early_stopping=True,
num_beams=2,
output_scores=True,
)
# document post-processing: sequence token cleaning
sequence = processor.batch_decode(outputs.sequences)[0]
sequence = sequence.replace(processor.tokenizer.eos_token, "").replace(processor.tokenizer.pad_token, "")
sequence = re.sub(r"<.*?>", "", sequence, count=1).strip() # remove first task start token
print(sequence)
# output conversion: token to json
output = processor.token2json(sequence)
return output
🔻 Mari kita coba men-generate 20 gambar receipt/invoice yang kita miliki!
# list untuk menyimpan hasil generate docs to text
preds = []
for img in images[:20]:
out = doc_to_text(img)
preds.append(out)
<s_menu><s_nm> REAL GANACHE</s_nm><s_cnt> 1</s_cnt><s_price> 16,500</s_price><sep/><s_nm> EGG TART</s_nm><s_cnt> 1</s_cnt><s_price> 13,000</s_price><sep/><s_nm> PIZZA TOAST</s_nm><s_cnt> 1</s_cnt><s_price> 16,000</s_price></s_menu><s_total><s_total_price> 45,500</s_total_price><s_cashprice> 50,000</s_cashprice><s_changeprice> 4,500</s_changeprice></s_total> <s_menu><s_nm> Kopi Susu Kolonel</s_nm><s_cnt> 1</s_cnt><s_price> 23.000</s_price></s_menu><s_total><s_total_price> 23.000</s_total_price><s_cashprice> 50.000</s_cashprice><s_changeprice> 27.000</s_changeprice></s_total> <s_menu><s_nm> S-Ovaltine</s_nm><s_unitprice> 20,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 20,000</s_price><s_sub><s_nm> 50%</s_nm></s_sub></s_menu><s_sub_total><s_subtotal_price> 18,181</s_subtotal_price><s_tax_price> 1,818</s_tax_price></s_sub_total><s_total><s_total_price> 20,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 80,000</s_changeprice></s_total> <s_menu><s_nm> M-Carame 1 Black Tea</s_nm><s_unitprice> @28,000</s_unitprice><s_cnt> 1X</s_cnt><s_price> 28,000</s_price><s_sub><s_nm> 70%</s_nm><sep/><s_nm> Less Ice</s_nm></s_sub></s_menu><s_sub_total><s_subtotal_price> 28,000</s_subtotal_price><s_tax_price> 0</s_tax_price></s_sub_total><s_total><s_total_price> 28,000</s_total_price><s_cashprice> 28,000</s_cashprice><s_changeprice> 0</s_changeprice></s_total> <s_menu><s_nm> BBQ Chicken</s_nm><s_cnt> 1</s_cnt><s_price> 41,000</s_price><s_sub><s_nm> - Sedang</s_nm><s_cnt> 1</s_cnt><s_price> 0</s_price></s_sub></s_menu><s_sub_total><s_subtotal_price> 41,000</s_subtotal_price></s_sub_total><s_total><s_total_price> 41,000</s_total_price><s_cashprice> 50.000</s_cashprice><s_changeprice> 9,000</s_changeprice><s_menuqty_cnt> 1</s_menuqty_cnt></s_total> <s_menu><s_nm> LE MINERAL</s_nm><s_price> 8,000</s_price></s_menu><s_sub_total><s_subtotal_price> 7,273</s_subtotal_price><s_tax_price> 727</s_tax_price></s_sub_total><s_total><s_total_price> 8,000</s_total_price><s_cashprice> 8,000</s_cashprice></s_total> <s_menu><s_nm> POTATO SAUSAGE BREAD</s_nm><s_cnt> 1</s_cnt><s_price> 19,000</s_price><sep/><s_nm> OREO GREEN TEA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price><sep/><s_nm> WHITE CHOCO BANANA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price></s_menu><s_total><s_total_price> 123,000</s_total_price><s_creditcardprice> 123,000</s_creditcardprice></s_total> <s_menu><s_nm> Choco Devil</s_nm><s_cnt> 4</s_cnt><s_price> 63,636</s_price><sep/><s_nm> CP 360 Club Card</s_nm><s_price> -9,545</s_price></s_menu><s_sub_total><s_subtotal_price> 63,636</s_subtotal_price><s_discount_price> -9,545</s_discount_price><s_tax_price> 5,409</s_tax_price></s_sub_total><s_total><s_total_price> 59,500</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 40,500</s_changeprice></s_total> <s_menu><s_nm> TALAM UNGU</s_nm><s_unitprice> @6500</s_unitprice><s_cnt> 3X</s_cnt><s_discountprice> -7,800</s_discountprice><s_price> 19,500</s_price><sep/><s_nm> MIKA KECIL</s_nm><s_unitprice> @0</s_unitprice><s_cnt> 1X</s_cnt><s_price> 0</s_price></s_menu><s_sub_total><s_subtotal_price> 11,700</s_subtotal_price></s_sub_total><s_total><s_total_price> 11,700</s_total_price><s_cashprice> 20,000</s_cashprice><s_changeprice> 8,300</s_changeprice><s_menuqty_cnt> 4.00xITEMs</s_menuqty_cnt></s_total> <s_menu><s_nm> Tahu Ikan Oma Giok</s_nm><s_cnt> 1</s_cnt><s_price> 20.000</s_price></s_menu><s_total><s_total_price> 20.000</s_total_price><s_cashprice> 20.000</s_cashprice><s_changeprice> 0</s_changeprice></s_total> <s_menu><s_nm> Serbu</s_nm><s_cnt> 2</s_cnt><s_price> 40.000</s_price><sep/><s_nm> Choco Peanut Bread</s_nm><s_cnt> 2</s_cnt><s_price> 20.000</s_price></s_menu><s_total><s_total_price> 60.000</s_total_price><s_cashprice> 60.000</s_cashprice><s_changeprice> 0</s_changeprice></s_total> <s_menu><s_nm> Se'I Sapi Sambal Matah ( R )</s_nm><s_cnt> 1</s_cnt><s_price> 20.000</s_price><sep/><s_nm> Se'I S-pi Lada Hitam (J)</s_nm><s_cnt> 1</s_cnt><s_price> 35.000</s_price><sep/><s_nm> Nasi Putih</s_nm><s_cnt> 2</s_cnt><s_price> 10.000</s_price><sep/><s_nm> Milk Shake Coklat</s_nm><s_cnt> 1</s_cnt><s_price> 16.000</s_price></s_menu><s_sub_total><s_subtotal_price> 81.000</s_subtotal_price><s_tax_price> 8.100</s_tax_price></s_sub_total><s_total><s_total_price> 89.100</s_total_price><s_changeprice> 0</s_changeprice><s_creditcardprice> 89.100</s_creditcardprice></s_total> <s_menu><s_nm> ES KOPI SUSU</s_nm><s_cnt> 4</s_cnt><s_price> 72.000</s_price></s_menu><s_total><s_total_price> 72.000</s_total_price><s_changeprice> 0</s_changeprice><s_emoneyprice> 72.000</s_emoneyprice></s_total> <s_menu><s_nm> MINERAL 600 ML</s_nm><s_cnt> 1</s_cnt><s_price> 7,727</s_price><sep/><s_nm> BULGOGI RICE R</s_nm><s_cnt> 1</s_cnt><s_price> 33,636</s_price></s_menu><s_sub_total><s_subtotal_price> 41,364</s_subtotal_price><s_tax_price> 4,136</s_tax_price></s_sub_total><s_total><s_total_price> 45,500</s_total_price><s_cashprice> 50,000</s_cashprice><s_changeprice> -4,500</s_changeprice></s_total> <s_menu><s_nm> Arem Arem</s_nm><s_unitprice> @12.000</s_unitprice><s_cnt> 2x</s_cnt><s_price> 24.000</s_price><sep/><s_nm> Kroket</s_nm><s_unitprice> @12.000</s_unitprice><s_cnt> 1x</s_cnt><s_price> 12.000</s_price></s_menu><s_sub_total><s_subtotal_price> Rp 36.000</s_subtotal_price><s_tax_price> Rp 3.600</s_tax_price></s_sub_total><s_total><s_total_price> Rp 39.600</s_total_price><s_emoneyprice> Rp 39.600</s_emoneyprice></s_total> <s_menu><s_nm> Arem Arem</s_nm><s_unitprice> 12.000</s_unitprice><s_cnt> 2</s_cnt><s_price> 24.000</s_price><sep/><s_nm> Pepenero Pastel</s_nm><s_unitprice> 15.000</s_unitprice><s_cnt> 2</s_cnt><s_price> 30.000</s_price></s_menu><s_sub_total><s_subtotal_price> Rp 54.000</s_subtotal_price><s_tax_price> Rp 5.400</s_tax_price></s_sub_total><s_total><s_total_price> Rp 59.400</s_total_price><s_cashprice> Rp 100.000</s_cashprice><s_changeprice> Rp 40.600</s_changeprice></s_total> <s_menu><s_nm> TT</s_nm><s_unitprice> 20,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 20,000</s_price></s_menu><s_total><s_total_price> 20,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 80,000</s_changeprice></s_total> <s_menu><s_nm> LEMONADE 16OZ</s_nm><s_unitprice> 20,000</s_unitprice><s_cnt> 1 x</s_cnt><s_price> 20,000</s_price></s_menu><s_sub_total><s_subtotal_price> 20,000</s_subtotal_price></s_sub_total><s_total><s_total_price> 20,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 80,000</s_changeprice></s_total> <s_menu><s_nm> beef c roll 3pcs</s_nm><s_unitprice> 10,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 10,000</s_price><sep/><s_nm> kaya bred</s_nm><s_unitprice> 15,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 15,000</s_price></s_menu><s_total><s_total_price> 25,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 75,000</s_changeprice><s_menuqty_cnt> 2</s_menuqty_cnt></s_total> <s_menu><s_nm> FUTAMI 17 GREEN TEA (CLAS</s_nm><s_cnt> 1</s_cnt><s_price> 12,500</s_price><sep/><s_nm> EGG TART</s_nm><s_cnt> 1</s_cnt><s_price> 13,000</s_price><sep/><s_nm> GRAIN CROQUE MONSIEUR</s_nm><s_cnt> 1</s_cnt><s_price> 17,000</s_price></s_menu><s_total><s_total_price> 42,500</s_total_price><s_cashprice> 50,000</s_cashprice><s_changeprice> 7,500</s_changeprice></s_total>
preds[0]
{'menu': [{'nm': 'REAL GANACHE', 'cnt': '1', 'price': '16,500'},
{'nm': 'EGG TART', 'cnt': '1', 'price': '13,000'},
{'nm': 'PIZZA TOAST', 'cnt': '1', 'price': '16,000'}],
'total': {'total_price': '45,500',
'cashprice': '50,000',
'changeprice': '4,500'}}
Data Analysis Enhancement with Donut Outputs¶
Agar mempermudah dalam kita menganalisis hasil model Donut maka kita perlu mengubah data generated text tersebut menjadi sebuah tabel.
Transform Donut Outputs to Dataframe pandas¶
Kita cukup menggunakan pd.DataFrame.from_dict(), pandas secara otomatis mengonversi struktur JSON tersebut ke dalam bentuk tabel.
df_preds = pd.DataFrame.from_dict(preds)
df_preds.head()
| menu | total | sub_total | |
|---|---|---|---|
| 0 | [{'nm': 'REAL GANACHE', 'cnt': '1', 'price': '... | {'total_price': '45,500', 'cashprice': '50,000... | NaN |
| 1 | {'nm': 'Kopi Susu Kolonel', 'cnt': '1', 'price... | {'total_price': '23.000', 'cashprice': '50.000... | NaN |
| 2 | {'nm': 'S-Ovaltine', 'unitprice': '20,000', 'c... | {'total_price': '20,000', 'cashprice': '100,00... | {'subtotal_price': '18,181', 'tax_price': '1,8... |
| 3 | {'nm': 'M-Carame 1 Black Tea', 'unitprice': '@... | {'total_price': '28,000', 'cashprice': '28,000... | {'subtotal_price': '28,000', 'tax_price': '0'} |
| 4 | {'nm': 'BBQ Chicken', 'cnt': '1', 'price': '41... | {'total_price': '41,000', 'cashprice': '50.000... | {'subtotal_price': '41,000'} |
📈 Insight: Setiap baris pada dataframe kita mewakili ...
🔻 Mari kita cek missing value-nya menggunakan metode isna().sum()
df_preds.isna().sum()
menu 0 total 0 sub_total 9 dtype: int64
📈 Insight: Dalam kasus kali ini kolom sub_total akan diabaikan karena kita akan mencoba mengambil total harga keseluruhan, harga per item, dan jumlah per item saja. Maka dari itu, kita tidak perlu membuat data yang subtotalnya kosong (NaN)
💭 Discussion¶
❓ Apakah menurut Anda, bentuk dataframe data invoice kita sudah cukup rapi/mudah dianalisis?
Kita memiliki data struk yang belum terorganisir dengan baik, dimana informasi menu, total, dan sub-total disajikan dalam satu baris.
Data Wrangling¶
🔻 Untuk memudahkan analisis, kita akan memecah data ini sehingga setiap item pada struk diwakili dalam baris tersendiri, lengkap dengan ID struknya. Pendekatan ini akan mempermudah pengolahan dan analisis data.
- Membuat dataframe kosong untuk menjadi tempat pengisian data kita.
df = pd.DataFrame(columns=['receipt_id', 'nm', 'cnt', 'price', 'total_price'])
df
| receipt_id | nm | cnt | price | total_price |
|---|
# cara mengecek jumlah baris pada suatu dataframe
len(df)
0
- Memasukkan seluruh data pada
df_predskedfdengan memanfaatkan looping.iterrows().
💡 .iterrows() merupakan metode pada objek DataFrame yang menghasilkan pasangan (index, row) untuk DataFrame tersebut
# mengiterasi setiap rrow dalam df_preds
for index, row in df_preds.iterrows():
# mengecek apakah field 'menu' adalah list, jika tidak, ubah menjadi list
menus = row['menu'] if isinstance(row['menu'], list) else [row['menu']]
# mengiterasi setiap elemen dalam list 'menus'
for menu in menus:
# menambahkan row baru ke 'df' dengan informasi dari 'menu' serta tambahan 'total_price' dan 'receipt_id'
df.loc[len(df)] = {
**menu, # unpack semua pasangan key-value dari dictionary 'menu' yang cocok dg nama kolom yg ada
'total_price': row['total']['total_price'], # mengambil 'total_price' dari baris saat ini
'receipt_id': img_filenames[index].split('.')[0] # mengambil ID struk dari nama file gambar
}
💡**dict : Melakukan unpack semua pasangan key-value dari dictionary 'dict'
💡df.loc[] : Metode yang digunakan untuk mengakses baris atau kolom tertentu dari DataFrame berdasarkan nama/labelnya
df
| receipt_id | nm | cnt | price | total_price | |
|---|---|---|---|---|---|
| 0 | ID0000 | REAL GANACHE | 1 | 16,500 | 45,500 |
| 1 | ID0000 | EGG TART | 1 | 13,000 | 45,500 |
| 2 | ID0000 | PIZZA TOAST | 1 | 16,000 | 45,500 |
| 3 | ID0001 | Kopi Susu Kolonel | 1 | 23.000 | 23.000 |
| 4 | ID0002 | S-Ovaltine | 1 | 20,000 | 20,000 |
| 5 | ID0003 | M-Carame 1 Black Tea | 1X | 28,000 | 28,000 |
| 6 | ID0004 | BBQ Chicken | 1 | 41,000 | 41,000 |
| 7 | ID0005 | LE MINERAL | NaN | 8,000 | 8,000 |
| 8 | ID0006 | POTATO SAUSAGE BREAD | 1 | 19,000 | 123,000 |
| 9 | ID0006 | OREO GREEN TEA SPREAD | 1 | 52,000 | 123,000 |
| 10 | ID0006 | WHITE CHOCO BANANA SPREAD | 1 | 52,000 | 123,000 |
| 11 | ID0007 | Choco Devil | 4 | 63,636 | 59,500 |
| 12 | ID0007 | CP 360 Club Card | NaN | -9,545 | 59,500 |
| 13 | ID0008 | TALAM UNGU | 3X | 19,500 | 11,700 |
| 14 | ID0008 | MIKA KECIL | 1X | 0 | 11,700 |
| 15 | ID0009 | Tahu Ikan Oma Giok | 1 | 20.000 | 20.000 |
| 16 | ID0010 | Serbu | 2 | 40.000 | 60.000 |
| 17 | ID0010 | Choco Peanut Bread | 2 | 20.000 | 60.000 |
| 18 | ID0011 | Se'I Sapi Sambal Matah ( R ) | 1 | 20.000 | 89.100 |
| 19 | ID0011 | Se'I S-pi Lada Hitam (J) | 1 | 35.000 | 89.100 |
| 20 | ID0011 | Nasi Putih | 2 | 10.000 | 89.100 |
| 21 | ID0011 | Milk Shake Coklat | 1 | 16.000 | 89.100 |
| 22 | ID0012 | ES KOPI SUSU | 4 | 72.000 | 72.000 |
| 23 | ID0013 | MINERAL 600 ML | 1 | 7,727 | 45,500 |
| 24 | ID0013 | BULGOGI RICE R | 1 | 33,636 | 45,500 |
| 25 | ID0014 | Arem Arem | 2x | 24.000 | Rp 39.600 |
| 26 | ID0014 | Kroket | 1x | 12.000 | Rp 39.600 |
| 27 | ID0015 | Arem Arem | 2 | 24.000 | Rp 59.400 |
| 28 | ID0015 | Pepenero Pastel | 2 | 30.000 | Rp 59.400 |
| 29 | ID0016 | TT | 1 | 20,000 | 20,000 |
| 30 | ID0017 | LEMONADE 16OZ | 1 x | 20,000 | 20,000 |
| 31 | ID0018 | beef c roll 3pcs | 1 | 10,000 | 25,000 |
| 32 | ID0018 | kaya bred | 1 | 15,000 | 25,000 |
| 33 | ID0019 | FUTAMI 17 GREEN TEA (CLAS | 1 | 12,500 | 42,500 |
| 34 | ID0019 | EGG TART | 1 | 13,000 | 42,500 |
| 35 | ID0019 | GRAIN CROQUE MONSIEUR | 1 | 17,000 | 42,500 |
📈 Insight: ...
🔻 Mari kita simpan data document extracted yang sudah kita lakukan agar terecord dengan baik
# menyimpan DataFrame 'df' ke dalam file csv
df.to_csv('receipt_extracted.csv',
index=False) # berarti indeks DataFrame tidak disertakan dalam file csv
Data Cleaning¶
# cek tipe data
df.info()
<class 'pandas.core.frame.DataFrame'> Index: 36 entries, 0 to 35 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 receipt_id 36 non-null object 1 nm 36 non-null object 2 cnt 34 non-null object 3 price 36 non-null object 4 total_price 36 non-null object dtypes: object(5) memory usage: 1.7+ KB
🔻Nama kolom masih kurang informatif, maka kita akan coba menggantinya menggunakan metode .rename()
new_columns_name = {
'nm': 'item_name',
'cnt': 'quantity'
}
df = df.rename(columns=new_columns_name)
df.head()
| receipt_id | item_name | quantity | price | total_price | |
|---|---|---|---|---|---|
| 0 | ID0000 | REAL GANACHE | 1 | 16,500 | 45,500 |
| 1 | ID0000 | EGG TART | 1 | 13,000 | 45,500 |
| 2 | ID0000 | PIZZA TOAST | 1 | 16,000 | 45,500 |
| 3 | ID0001 | Kopi Susu Kolonel | 1 | 23.000 | 23.000 |
| 4 | ID0002 | S-Ovaltine | 1 | 20,000 | 20,000 |
🔻 Jika kita lihat kembali kolom price maupun total_price seharusnya memiliki tipe data int/float. Maka dari itu kita perlu melakukan pembersihan ,.
# fungsi untuk membersihkan string harga dari simbol dan spasi
def clean_price(x):
return int(x.replace(".", "").replace(",", "").replace("Rp", "").replace(" ", ""))
Mari kita coba aplikasikan fungsi clean_price terhadap salah satu baris pada data kita
clean_price(df.loc[0, 'price'])
16500
❓ Lalu, bagaimana cara mengaplikasikan fungsi tersebut untuk semua baris tanpa kita melakukan satu persatu?
Jawabannya, kita bisa memanfaatkan fungsi
.apply()
💡 Fungsi apply memungkinkan kita untuk menjalankan suatu fungsi terhadap setiap elemen dalam sebuah kolom secara otomatis. Contohnya, jika kita ingin membersihkan format harga dalam kolom 'price' di DataFrame df, kita bisa melakukan hal berikut:
# mengaplikasikan fungsi 'clean_price' ke setiap elemen di kolom 'price' pada 'df'
df['price'] = df['price'].apply(lambda x: clean_price(str(x)))
# mengaplikasikan fungsi 'clean_price' ke setiap elemen di kolom 'total_price' pada 'df'
df['total_price'] = df['total_price'].apply(lambda x: clean_price(x))
🔻 Selanjutnya, kita akan coba membersihkan kolom quantity seharusnya memiliki tipe data int/float. Maka dari itu kita perlu melakukan pembersihan ,.
, serta X
Mari kita buat custom fungsinya terlebih dahulu:
# membersihkan dan mengkonversi nilai 'x' menjadi float
def clean_quantity(x):
# jika 'x' bernilai None (tidak ada data), kembalikan 0
if x is None:
return 0
# mengonversi 'x' ke string, menghapus spasi, 'x'/'X', dan ']' dari string tersebut, dan mengembalikan menjadi sebuah angka
return float(str(x).replace(" ", "").replace("x", "").replace("X", "").replace("]", ""))
# mengaplikasikan fungsi 'clean_quantity' ke setiap elemen di kolom 'quantity' pada 'df'
df['quantity'] = df['quantity'].apply(lambda x: clean_quantity(x))
🤿 (Opsional) Dive Deeper: 🍩 Advanced Document Extracted Dataframe¶
Misalkan Anda bekerja sebagai data scientist di sebuah supermarket dengan dataset yang mencatat transaksi penjualannya. Dataset ini berisi informasi tentang ID struk (receipt_id), harga total per struk (total_price), dan nama item yang terjual (item_name).
Untuk mendapatkan wawasan yang lebih terstruktur dari data penjualan ini, Anda diminta membuat tabel pivot yang mengelompokkan dan menyajikan data berdasarkan receipt_id, total_price.
# code here
df_pivot = df.pivot_table(index=['receipt_id', 'total_price', 'item_name'])
df_pivot
| price | quantity | |||
|---|---|---|---|---|
| receipt_id | total_price | item_name | ||
| ID0000 | 45500 | EGG TART | 13000.0 | 1.0 |
| PIZZA TOAST | 16000.0 | 1.0 | ||
| REAL GANACHE | 16500.0 | 1.0 | ||
| ID0001 | 23000 | Kopi Susu Kolonel | 23000.0 | 1.0 |
| ID0002 | 20000 | S-Ovaltine | 20000.0 | 1.0 |
| ID0003 | 28000 | M-Carame 1 Black Tea | 28000.0 | 1.0 |
| ID0004 | 41000 | BBQ Chicken | 41000.0 | 1.0 |
| ID0005 | 8000 | LE MINERAL | 8000.0 | NaN |
| ID0006 | 123000 | OREO GREEN TEA SPREAD | 52000.0 | 1.0 |
| POTATO SAUSAGE BREAD | 19000.0 | 1.0 | ||
| WHITE CHOCO BANANA SPREAD | 52000.0 | 1.0 | ||
| ID0007 | 59500 | CP 360 Club Card | -9545.0 | NaN |
| Choco Devil | 63636.0 | 4.0 | ||
| ID0008 | 11700 | MIKA KECIL | 0.0 | 1.0 |
| TALAM UNGU | 19500.0 | 3.0 | ||
| ID0009 | 20000 | Tahu Ikan Oma Giok | 20000.0 | 1.0 |
| ID0010 | 60000 | Choco Peanut Bread | 20000.0 | 2.0 |
| Serbu | 40000.0 | 2.0 | ||
| ID0011 | 89100 | Milk Shake Coklat | 16000.0 | 1.0 |
| Nasi Putih | 10000.0 | 2.0 | ||
| Se'I S-pi Lada Hitam (J) | 35000.0 | 1.0 | ||
| Se'I Sapi Sambal Matah ( R ) | 20000.0 | 1.0 | ||
| ID0012 | 72000 | ES KOPI SUSU | 72000.0 | 4.0 |
| ID0013 | 45500 | BULGOGI RICE R | 33636.0 | 1.0 |
| MINERAL 600 ML | 7727.0 | 1.0 | ||
| ID0014 | 39600 | Arem Arem | 24000.0 | 2.0 |
| Kroket | 12000.0 | 1.0 | ||
| ID0015 | 59400 | Arem Arem | 24000.0 | 2.0 |
| Pepenero Pastel | 30000.0 | 2.0 | ||
| ID0016 | 20000 | TT | 20000.0 | 1.0 |
| ID0017 | 20000 | LEMONADE 16OZ | 20000.0 | 1.0 |
| ID0018 | 25000 | beef c roll 3pcs | 10000.0 | 1.0 |
| kaya bred | 15000.0 | 1.0 | ||
| ID0019 | 42500 | EGG TART | 13000.0 | 1.0 |
| FUTAMI 17 GREEN TEA (CLAS | 12500.0 | 1.0 | ||
| GRAIN CROQUE MONSIEUR | 17000.0 | 1.0 |
Integration Gradio for Efficient Scanning¶
🛠️ Gradio adalah sebuah library Python yang digunakan untuk membangun antarmuka web yang mudah digunakan untuk aplikasi berbasis machine learning. Dengan Gradio, kita dapat dengan cepat membuat antarmuka untuk model machine learning sehingga pengguna dapat dengan mudah berinteraksi dengannya tanpa harus memiliki pengetahuan teknis yang mendalam tentang pemrograman atau machine learning.
Berikut adalah contoh web-app menggunakan Gradio:

Understand the Fundamentals of Gradio¶
gr.Interface() adalah kelas utama dalam Gradio yang digunakan untuk membuat antarmuka pengguna (UI) interaktif untuk model machine learning atau fungsi Python lainnya. Ini memungkinkan developer dengan cepat membuat aplikasi web yang dapat menerima input dari pengguna dan memberikan output atau hasil dari model atau fungsi yang diintegrasikan.
Berikut adalah konsep utama dan parameter gr.Interface:
import gradio as gr
c:\Users\user\miniconda3\envs\dss_donut\lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm
Main Concept:¶
gr.Interface(input=, output=, fn=, live=)
📥Inputs ("input"): Menentukan jenis input yang akan diterima oleh antarmuka. Contoh:
"text","image","video","json","audio"atau kombinasi"text","image".📤Outputs ("output"): Menentukan jenis output yang akan dihasilkan oleh antarmuka. Contoh:
"text","image","video","json","audio"atau kombinasi"text","image".⚒️ Function ("fn"): Fungsi Python yang akan dijalankan ketika ada input dari pengguna. Bisa berupa model machine learning atau fungsi khusus lainnya.
🌐 Share ("share"): Boolean, menentukan apakah dibuatkan public link atau tidak (temporary 72 hours deployment). Contoh:
TrueatauFalse.
Additional gr.Interface Parameters:¶
title: Judul antarmuka yang akan ditampilkan di halaman web. Contoh:"My Gradio App".description: Deskripsi antarmuka yang akan ditampilkan di halaman web. Contoh:"A simple app for image classification".sidebar: Menentukan apakah sidebar akan ditampilkan atau tidak. Contoh:TrueatauFalse.live: Menentukan apakah antarmuka akan memberikan pembaruan langsung. Contoh:TrueatauFalse.
Simple Examples of Usage¶
Berikut adalah contoh sederhana penggunaan gr.Interface:
import gradio as gr
def my_function(name_text):
return f"Hello, {name_text}. Nice to meet you!"
test = gr.Interface(fn=my_function, inputs="text", outputs="text", live=True, title="Hello World App")
test.launch()
Running on local URL: http://127.0.0.1:7861 To create a public link, set `share=True` in `launch()`.
📈 Insight: Pada contoh di atas, kita membuat antarmuka yang menerima input teks dan mengembalikan output teks yang merupakan cetakan dari input pengguna.Kita dapat menggabungkan input dan output yang berbeda serta menyesuaikan parameter sesuai kebutuhan aplikasi yang dibangun.
Develop Gradio for Real-time Receipt and Invoice Scanning¶
🔻 Mari kita buat aplikasi aplikasi Gradio yang memungkinkan pengguna melakukan pemindaian dan ekstraksi teks secara real-time pada struktur dokumen seperti kwitansi dan invoice dengan memanfaatkan fungsi doc_to_text yang telah kita rancang sebelumnya.
model.eval()
demo = gr.Interface(
fn=doc_to_text,
inputs="image",
outputs="json",
title=f"🗃️ Streamlining Invoice Processing: Document Understanding Transformer",
description="Extract text from receipts and invoices instantly.",
)
demo.launch(debug=True, share=True)
⚒️ Keunggulan Gradio with Donut:
Efisiensi dan Kepresisian: Mampu mengekstrak teks dari dokumen secara cepat dan akurat, menghemat waktu tanpa perlu proses manual.
Peningkatan Produktivitas: Mempermudah pengelolaan informasi dari kwitansi dan faktur, meningkatkan produktivitas dalam pencatatan keuangan dan pelacakan pengeluaran sehari-hari.
Aksesibilitas dan Kemudahan Penggunaan: Gradio memberikan antarmuka yang intuitif, memungkinkan pengguna mengunggah gambar dokumen dengan cepat tanpa memerlukan keahlian teknis.
Dengan demikian, aplikasi ini tidak hanya berpotensi memberikan solusi teknis, tetapi juga meningkatkan efisiensi dan kenyamanan dalam mengelola informasi dari dokumen struktural seperti kwitansi dan faktur.
(Opsional): Deploy the Gradio¶
✨ Tutorial:
[Additional] 🛠️ Tools Verification¶
- Membuat environtmet conda bernama
dss_donutdengan python=3.10 - Aktifkan environtment dengan
conda activate dss_donut - Lakukan
pip install -r requirements.txt
import re
import os
import torch
import gradio as gr
from PIL import Image
from transformers import DonutProcessor, VisionEncoderDecoderModel
processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")